package picsim;


/**
 *
 * @author Daniel
 * @brief Übernimmt verschiedene Berechnungen und Auswertungen, zur weiteren Verwendung
 */
public class Calculator {

    private final int MASK1 = 0xFF00;           // 1111 1111 0000 0000 (dual)
    private final int MASK2 = 0xFC00;           // 1111 1100 0000 0000 (dual)
    private final int MASK3 = 0xF800;           // 1111 1000 0000 0000 (dual)
    private final int MASK1_f = 0x007F;         // 0000 0000 0111 1111 (dual)

    private final int MASK1_SPECIAL = 0xFF80;   // 1111 1111 1000 0000 (dual)
    private final int MASK2_B = 0x0380;         // 0000 0011 1000 0000 (dual)
    
    private int opcodeTable[] = new int[35];    // table for instruction codes (hex)

    /**
     * @brief Konstruktor der Klasse Calculator
     */
    public Calculator() {
        fillOpcodeTable();
    }

    /**
     * @brief Füllt die Tabelle "opcodeTable" mit den entsprechenden hex-Werten der Befehle
     */
    public void fillOpcodeTable() {

        opcodeTable[0] = 0x0700; //addwf
        opcodeTable[1] = 0x0500; //andwf
        opcodeTable[2] = 0x0100; //clrf
        opcodeTable[3] = 0x0100; //clrw
        opcodeTable[4] = 0x0900; //comf
        opcodeTable[5] = 0x0300; //decf
        opcodeTable[6] = 0x0B00; //decfsz
        opcodeTable[7] = 0x0A00; //incf
        opcodeTable[8] = 0x0F00; //incfsz
        opcodeTable[9] = 0x0400; //iorwf
        opcodeTable[10] = 0x0800; //movf
        opcodeTable[11] = 0x0080; //movwf
        opcodeTable[12] = 0x0000; //nop
        opcodeTable[13] = 0x0D00; //rlf
        opcodeTable[14] = 0x0C00; //rrf
        opcodeTable[15] = 0x0200; //subwf
        opcodeTable[16] = 0x0E00; //swapf
        opcodeTable[17] = 0x0600; //xorwf

        opcodeTable[18] = 0x1000; //bcf
        opcodeTable[19] = 0x1400; //bsf
        opcodeTable[20] = 0x1800; //btfsc
        opcodeTable[21] = 0x1C00; //btfss

        opcodeTable[22] = 0x3E00; //addlw
        opcodeTable[23] = 0x3900; //andlw
        opcodeTable[24] = 0x2000; //call
        opcodeTable[25] = 0x0064; //clrwdt
        opcodeTable[26] = 0x2800; //goto
        opcodeTable[27] = 0x3800; //iorlw
        opcodeTable[28] = 0x3000; //movlw
        opcodeTable[29] = 0x0009; //retfie
        opcodeTable[30] = 0x3400; //retlw
        opcodeTable[31] = 0x0008; //return
        opcodeTable[32] = 0x0063; //sleep
        opcodeTable[33] = 0x3C00; //sublw
        opcodeTable[34] = 0x3A00; //xorlw

    }

    /**
     * 
     * @param instruction (int)
     * @return name (String)
     * @brief Ermittelt anhand des übergebenen hex-werts des Befehls den Namen des Befehls
     */
    public String getInstructionName(int instruction) {

        String name = null;
        int index = getOpcodeTableIndex(instruction);

        switch (index) {
            case 0:
                name = "addwf";
                break;
            case 1:
                name = "andwf";
                break;
            case 2:
                name = "clrf";
                break;
            case 3:
                name = "clrw";
                break;
            case 4:
                name = "comf";
                break;
            case 5:
                name = "decf";
                break;
            case 6:
                name = "decfsz";
                break;
            case 7:
                name = "incf";
                break;
            case 8:
                name = "incfsz";
                break;
            case 9:
                name = "iorwf";
                break;
            case 10:
                name = "movf";
                break;
            case 11:
                name = "movwf";
                break;
            case 12:
                name = "nop";
                break;
            case 13:
                name = "rlf";
                break;
            case 14:
                name = "rrf";
                break;
            case 15:
                name = "subwf";
                break;
            case 16:
                name = "swapf";
                break;
            case 17:
                name = "xorwf";
                break;

            case 18:
                name = "bcf";
                break;
            case 19:
                name = "bsf";
                break;
            case 20:
                name = "btfsc";
                break;
            case 21:
                name = "btfss";
                break;

            case 22:
                name = "addlw";
                break;
            case 23:
                name = "andlw";
                break;
            case 24:
                name = "call";
                break;
            case 25:
                name = "clrwdt";
                break;
            case 26:
                name = "goto";
                break;
            case 27:
                name = "iorlw";
                break;
            case 28:
                name = "movlw";
                break;
            case 29:
                name = "retfie";
                break;
            case 30:
                name = "retlw";
                break;
            case 31:
                name = "return";
                break;
            case 32:
                name = "sleep";
                break;
            case 33:
                name = "sublw";
                break;
            case 34:
                name = "xorlw";
                break;

            default:
                name = "false";


        }

        return name;

    }

    /**
     * 
     * @param hexValue (int)
     * @return index (int)
     * @brief Durchsucht die Tabelle opcodeTable nach dem übergebenen hex-Wert des Befehls und gibt den Index zurück
     */
    public int getOpcodeTableIndex(int hexValue) {

        int index = 0;

        for (int i = 0; i < opcodeTable.length; i++) {
            if (opcodeTable[i] == hexValue) {
                index = i;
                break;
            }
        }

        return index;

    }

    /**
     *
     * @param command
     * @return mask (String)
     * @brief Gibt die entsprechende Maske anhand des übergebenen Befehls (command)
     */
    public int getMask(String command) {

        int mask = 0;
        command = command.trim();
        int command_int = Integer.parseInt(command, 16);
        int cmd = command_int & 0xF000;

        switch (cmd) {
            // BYTE-ORIENTED FILE REGISTER OPERATIONS
            case 0x0000:
            case 0x3000:
                //System.out.println("maske1 anwenden");
                mask = 1;
                break;

            // BIT-ORIENTED FILE REGISTER OPERATIONS
            case 0x1000:
                //System.out.println("maske2 anwenden");
                mask = 2;
                break;

            // LITERAL AND CONTROL OPERATIONS            
            case 0x2000:
                //System.out.println("maske3 anwenden");
                mask = 3;
                break;

            default:
                System.out.println("[Calculator.chooseTemplate():] Fehler...");
        }

        return mask;

    }

    /**
     * 
     * @param mask (int)
     * @param opcode (String)
     * @return instruction (int)
     * @brief Anhand der Maske und dem Opcode liefert die Methode den hex-wert der Operation/des Befehls
     */
    public int getInstruction(int mask, String opcode) {

        int instruction = 0;
        int code = Integer.parseInt(opcode, 16);

        if (mask == 1) {
            // use MASK1
            instruction = code & MASK1;
        } else if (mask == 2) {
            // use MASK2
            instruction = code & MASK2;
        } else if (mask == 3) {
            // use MASK3
            instruction = code & MASK3;
        }

        return instruction;
    }

    /**
     * 
     * @param mask (int)
     * @param opcode (String)
     * @return address (int)
     * @brief Anhand der vorher ermittelten Maske und dem Opcode liefert die Methode den hex-wert der Zieladresse
     */
    public int getRegisterAddress(int mask, String opcode) {

        int address = 0;
        int code = Integer.parseInt(opcode, 16);


        if (mask == 1) {
            // use MASK1
            address = code & MASK1_f;
        } else if (mask == 2) {
            // use MASK2
            address = code & ~MASK2;
        } else if (mask == 3) {
            // use MASK3
            address = code & ~MASK3;
        } else if (mask == 4) {
            // use MASK1_SPECIAL
            address = code & ~MASK1_SPECIAL;
        } else if (mask == 5) {
            // use MASK2_B
            address = code & MASK2_B;
        }
        else if (mask == 6) {
            // use MASK1
            address = code & ~MASK1;
        }

        return address;
    }


    /**
     *
     * @param opcode (String)
     * @return name (String)
     * @brief Gibt den Namen der Befehle zurück, die vollständig vorgegeben sind
     */
    public String getWellDefinedInstructionName(String opcode) {

        String name = "";
        int code = Integer.parseInt(opcode, 16);

        name = getInstructionName(code);

        return name;

    }

    /**
     * 
     * @param opcode (String)
     * @param bit (int)
     * @return isSet (boolean)
     * @brief Prüft, ob das Bit an der angegebenen Stelle (<code>bit</code>) des übergebenen <code>opcode</code> gesetzt ist.
     */
    public boolean isBitSet(String opcode, int bit) {

        boolean isSet;
        int code = Integer.parseInt(opcode, 16), diff = 0;

        String tmp = Integer.toBinaryString(code);
        
        // fill String to 2Byte        
        diff = 16 - tmp.length();
        
        for (int i = 0; i < diff; i++) {
            tmp = "0" + tmp;
        }

        // get bit of String on specified index
        String value = tmp.substring(bit, bit + 1);

        // test whether bit is set or not
        if (value.equals("1")) {
            isSet = true;
        } else {
            isSet = false;
        }

        return isSet;
    }

    /**
     * 
     * @param memAddr (int)
     * @return memAddr (String)
     * @brief Füllt die übergebene <code>memAddr</code> auf 4 Bit auf (es werden führende Nullen hinzugefügt)
     */
    public String fillMemoryAddress(int memAddr) {

        String memAddr_string = "";

        memAddr_string = Integer.toHexString(memAddr);

        // fill String to 4 bit
        int diff = 4 - memAddr_string.length();

        for (int i = 0; i < diff; i++) {
            memAddr_string = "0" + memAddr_string;
        }

        return memAddr_string;
    }

    /**
     * 
     * @param size (int)
     * @param value (int)
     * @return binaryString (String)
     * @brief Füllt den übergebenen Wert (<code>value</code>) mit führenden Nullen
     * auf, bis die Länge des Strings der übergebenen Länge (<code>size</code>) entspricht.
     */
    public String fillToBinaryString(int size, int value) {

        String tmp = "";

        tmp = Integer.toBinaryString(value);

        // fill String to <size> bit
        int diff = size - tmp.length();

        for (int i = 0; i < diff; i++) {
            tmp = "0" + tmp;
        }

        return tmp;

    }

    /**
     * 
     * @param size (int)
     * @param value (int)
     * @return hexString (String)
     * @brief Füllt den übergebenen Wert (<code>value</code>) mit führenden Nullen
     * auf, bis die Länge des Strings der übergebenen Länge (<code>size</code>) entspricht.
     */
    public String fillToHexString(int size, int value) {

        String tmp = "";

        tmp = Integer.toHexString(value);
        tmp = tmp.toUpperCase();

        // fill String to <size> bit
        int diff = size - tmp.length();

        for (int i = 0; i < diff; i++) {
            tmp = "0" + tmp;
        }

        return tmp;

    }

    /**
     * 
     * @param size (int)
     * @param value (int)
     * @return values[] (int Array)
     * @brief Gibt die einzelnen Bits des übergebenen Wertes (<code>value</code>) zurück.
     * Die Größe des Arrays wird durch den Parameter <code>size</code> angegeben.
     */
    public int[] getSingleBitValues(int size, int value) {

        int values[] = new int[size];

        String value_string = fillToBinaryString(size, value);

        int x = size;

        for(int i=0; i<size; i++) {
            values[i] = Integer.parseInt(value_string.substring(x-1, x));
            x--;
        }

        return values;

    }


    /**
     * 
     * @param time (int)
     * @return time (String)
     * @brief Rechnet die übergebene Zeit in eine besser lesbare Zeit um (falls nötig)
     */
    public String convertTimeToString(int time) {

        String newTime = "";
        double calc = 0;

        if(time < 999) {
            newTime = Integer.toString(time)+" µs";
        }

        if(time >= 1000) { // now we have ms to show!
            calc = (double) time / 1000;
            newTime = Double.toString(calc)+" ms";
        }

        if(time >= 1000000) { // now we have s to show!
            calc = (double) time / 1000000;
            newTime = Double.toString(calc)+" s";
        }

        return newTime;

    }

}
